#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#include "Util.h"


typedef enum {
	SEQUENTIAL, RANDOMOFFSET_SEQUENTIAL, RANDOM
} readType_t;

struct workLoadParams {
	int option;
	char *buffer;
	long bufferSize;
	long numReads;
	int numSeqReads;
};

void doSequentialReads(char *buffer, long bufferSize, long numReads, int numSeqReads);
void doRandOffsetAndSequentialReads(char *buffer, long bufferSize,
		long numReads, int numSeqReads);
void doRandomReads(char *buffer, long bufferSize, long numReads, int numSeqReads);
void* runWorkload(void* ptr);

struct workLoadParams* constructReadParams(int option, char *buffer, long bufferSize, long numReads, int numSeqReads);

int main(int argc, char *argv[]) {
	long bufferSize, numReads;
	int option = 0, numSeqReads = 0;
	char *buffer = NULL;
	struct timespec startTime, endTime;
	double timeToDoMalloc = 0;
	readType_t readType;
    struct workLoadParams* params;

	validateAndFill2InputArgumentsWithOption(argc, argv, &bufferSize,
			"BufferSize", &numReads, "NumReads", &option, "Option", &numSeqReads, "numSeqReads");
	readType = option;


	srand(time(NULL ));
	clock_gettime(CLOCK_REALTIME, &startTime);
	buffer = (char*) malloc(sizeof(char) * bufferSize);
	if (buffer == NULL ) {
		perror("Error while allocating memory to buffer\n");
		return EXIT_FAILURE;
	} else {
		fillAllocatedMemory(buffer, bufferSize);
	}
	clock_gettime(CLOCK_REALTIME, &endTime);
	timeToDoMalloc = getDiffInNanoSeconds(startTime, endTime);

	params = constructReadParams(option, buffer, bufferSize, numReads,numSeqReads);

	runWorkload(params);

	free(buffer);
	return EXIT_SUCCESS;
}

void* runWorkload(void* ptr) {
    double timeForReads;
    struct timespec startTime, endTime;
    struct workLoadParams* param = (struct workLoadParams*) ptr;

    readType_t readType = param->option;
    char *buffer = param->buffer;
    long bufferSize = param->bufferSize;
    long numReads = param->numReads;
    int numSeqReads = param->numSeqReads;

	clock_gettime(CLOCK_REALTIME, &startTime);
	switch (readType) {
	case SEQUENTIAL:
		doSequentialReads(buffer, bufferSize, numReads, numSeqReads);
		break;
	case RANDOMOFFSET_SEQUENTIAL:
		doRandOffsetAndSequentialReads(buffer, bufferSize, numReads, numSeqReads);
		break;
	case RANDOM:
		doRandomReads(buffer, bufferSize, numReads, numSeqReads);
		break;
	}
	clock_gettime(CLOCK_REALTIME, &endTime);
	timeForReads = getDiffInNanoSeconds(startTime, endTime);

	printf("%f %lu %f\n", (double) bufferSize / ONE_MB,  numReads, (double) timeForReads / (double) numReads);
	return NULL;
}

struct workLoadParams* constructReadParams(int option, char *buffer, long bufferSize, long numReads, int numSeqReads) {
	struct workLoadParams *param = (struct workLoadParams*) malloc(sizeof(struct workLoadParams));
	param->option = option;
	param->buffer = buffer;
	param->bufferSize = bufferSize;
	param->numReads = numReads;
	param->numSeqReads = numSeqReads;
	return param;
}

void doSequentialReads(char *buffer, long bufferSize, long numReads,int numSeqReads) {
	long offset = 0, nextOffset = 0;
	int counter = 0, INCREMENT = 1;
	char c;
	offset = rand() % bufferSize;
	for (counter = 0; counter < numReads; counter++) {
		nextOffset = getNextOffsetWithinPage(offset, INCREMENT, bufferSize);
		c = buffer[offset];
		offset = nextOffset;
	}
}

void doRandOffsetAndSequentialReads(char *buffer, long bufferSize,
		long numReads,int numSeqReads) {
	long numRandOffsets = numReads / numSeqReads, offset = 0,
			nextOffset = 0;
	int counter = 0, INCREMENT = 1, i = 0;
	char c;
	for (counter = 0; counter < numRandOffsets; counter++) {
		offset = rand() % bufferSize;
		for (i = 0; i < numSeqReads; i++) {
			nextOffset = getNextOffsetWithinPage(offset, INCREMENT, bufferSize);
			c = buffer[offset];
			offset = nextOffset;
		}
	}
}

void doRandomReads(char *buffer, long bufferSize, long numReads, int numSeqReads) {
	long offset = 0, nextOffset;
	int counter = 0, randIncrement = 0;
	char c;
	for (counter = 0; counter < numReads; counter++) {
		offset = rand() % bufferSize;
		c = buffer[offset];
	}
}
